Skip to content

feat(design): show field errors from the API - #576

Open
uniqueg wants to merge 12 commits into
devfrom
feat/535-field-errors
Open

uniqueg wants to merge 12 commits into
devfrom
feat/535-field-errors

Conversation

@uniqueg

@uniqueg uniqueg commented Nov 13, 2026 •

Copy link
Copy Markdown
Member

Summary

Generic support in @elixir-cloud/design for showing the field errors that an API returns, without validation rules in the components:

  • Form controls (input, textarea, select, multi-select) get the properties invalid (Boolean, reflected) and error (string); the checkbox gets invalid only (per review: a message under the box doesn't wrap and overflows when the checkbox is at the end of a row). error shows the message under the control (part error, aria-live="polite") and marks the control as invalid: red border (destructive token), aria-invalid="true" and aria-describedby pointing to the message. invalid marks it as invalid without a message. For select and multi-select, the properties are on the root element; the trigger shows the invalid state and keeps a hidden copy of the message for aria-describedby, which can't refer to another shadow root.
  • Problem details utilities (src/utils/problem-details.ts, exported from the new subpath @elixir-cloud/design/utils, which registers no components, and from the package root), server-agnostic, for RFC 9457 (application/problem+json):
    • readProblemDetails(response) / parseProblemDetails(body): standard members (type defaulting to about:blank, title, status, detail, instance), field errors in the form of the RFC's example (errors[] with detail and pointer as URI fragment, e.g. #/organization/url; plain pointers and parameter accepted too; code kept; each entry as received in raw), extension members (e.g. code) in extensions.
    • mapFieldErrors(problem, fields, fallback?): maps the errors to the component's fields by pointer or parameter (exact match, else the closest containing field, e.g. /tags/0 to /tags). Errors that match no field, problem details without errors and responses that aren't problem details give generalError (the unmatched messages with their locations, else detail/title, else the fallback).
    • normalizePointer(pointer).
    • The format matches FOCA's invalid-request problem type (feat: report invalid fields in 400 responses foca#586, foca#765), but nothing is FOCA-specific.
  • Build: ./utils subpath export in the design package's package.json, and src/utils/index.ts as a tsup entry. The entries move from scripts/build.js to getBuildEntries() in scripts/utils.js (unchanged for every package apart from the utils entry), with tests in scripts/utils.test.js.
  • Docs: new page Design > Field errors (format, utilities, example component); invalid/error on the pages of the four controls, invalid on the checkbox page; the page imports the utilities from @elixir-cloud/design/utils.

No client package is changed; components can adopt this in follow-ups (e.g. the Cloud Registry create form). Changeset: .changeset/design-field-errors.md (@elixir-cloud/design: minor), following the convention that arrived on dev with #516 (merged into this branch).

Closes #535

Verification

  • npm run lint (repo root; builds all packages and the docs site, then ESLint): 19/19 tasks successful, no errors (warnings only, all pre-existing kinds).
  • Design package tests (web-test-runner, --group default): Chromium 58 passed, Firefox 58 passed (1 skipped). New: src/form-controls.test.ts (states, aria wiring, attribute, clearing, select/multi-select triggers, trigger without select) and src/utils/problem-details.test.ts (pointer normalization, parsing incl. wrong member types and non-problem bodies, readProblemDetails content types, mapping incl. unknown pointers/parameters, ancestors, responses without errors, fallback, RFC 6901 escapes, field keys like toString).
  • Not run locally: WebKit (browser not installed on this machine; CI runs it). One existing test (GlobalStyles > code editor theme > draws fold arrows) failed locally on Chromium only because the sandbox blocks the Ace worker download from cdn.jsdelivr.net; unrelated to this change.
  • CI (after the Sourcery fixes and merging dev): all checks pass (CI passed, Lint, Test on Node 22 and 24 incl. WebKit, Conflict markers, PR title, CodeQL, Codecov patch/project, Read the Docs, Sourcery review). Sourcery's 9 threads were addressed (8 fixed, 1 answered: the focus ring width is unchanged) and resolved; Sourcery approved.

After the maintainer's review

Changes for @anuragxxd's review (commits 2f14e7e..29d1097):

  • ./utils subpath export (exports plus typesVersions for TypeScript's node resolution) and src/utils/index.ts as a tsup entry; CI step "Check design utils entry" (scripts/check-design-utils.mjs) checks after the build that the subpath exports the utilities and loads neither Lit nor components (verified to fail when a component import is added).
  • Checkbox: error removed, invalid kept; docs say how to show the message instead.

Local checks: node --test scripts/utils.test.js scripts/release.test.js 32/32 pass; design package tests (web-test-runner --group default, Chromium and Firefox; WebKit can't be installed on Ubuntu 26.04, CI runs it) all pass; npm run lint (builds all packages and docs) 0 errors; node scripts/check-design-utils.mjs ok; tsc resolves @elixir-cloud/design/utils under node, node16 and bundler resolution. Reviewed by the adversarial and quality reviewer subagents (2 rounds).

Declined review findings

  • Problem-details entries without a detail are dropped (not shown at a field): RFC 9457's example and FOCA always send detail; inventing a message would be guesswork.

  • Error-state markup and logic is repeated in the five controls rather than shared: the repo's Tailwind build generates styles per component directory from that directory's files, so the classes must stay in each component, and the components already duplicate helpers (e.g. cn) in the same way.

  • The select roots call requestUpdate() on their triggers instead of extending the polled selectState: it updates at once and is explained in a comment; the shared state is for open/value.

  • A field key __proto__ would lose its message: not a realistic field key.

  • field-errors.mdx is formatted with the docs app's Prettier settings (single quotes), while the component pages keep their existing style; reformatting those pages is out of scope.

  • #-pointer errors without location are covered by the general-error test; normalizePointer is kept public and documented, as components need it to compare their own pointers.

  • normalizePointer stays in the CI check's expected exports: it is documented as public on the Field errors page.

  • typesVersions for /components/*, /react and /events (also unresolvable under TypeScript's node resolution, as before this PR): separate improvement, not part of this review.

Open questions

None.

🤖 Generated with Claude Code

uniqueg and others added 5 commits November 13, 2026 08:05
Input, textarea, checkbox, select and multi-select get the properties
invalid and error. error shows a message under the control (part
"error", announced with aria-live="polite") and marks the control as
invalid: red border, aria-invalid and aria-describedby. The select
triggers take the state from their select and keep a screen reader
copy of the message, as aria-describedby can't cross shadow roots.

Refs #535

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add server-agnostic utilities for RFC 9457 problem details:
readProblemDetails() and parseProblemDetails() read the standard
members, the field errors (errors[] with detail and pointer, as in the
RFC's example; plain pointers and parameter accepted) and the extension
members; mapFieldErrors() maps the errors to a component's fields by
pointer or parameter (closest containing field otherwise) and gives a
general error for the rest, for problem details without errors and for
other responses.

Refs #535

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add a Field errors page (response format, utilities, example) and the
invalid and error properties to the pages of the form controls.

Refs #535

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- mapFieldErrors() takes parsed problem details only, instead of
  telling them from bodies by a hidden registry of parsed objects.
- Field keys that are names of object members (e.g. toString) map
  correctly; locations in the general error have their keys
  unescaped (RFC 6901).
- The checkbox message no longer widens or shifts the box next to its
  label; the select triggers' copy for aria-describedby is hidden, so
  screen readers don't read the message twice.
- Docs: errors aren't passed down to contained fields; the example
  handles network errors; FOCA's problem type linked.
- More tests for the select controls.

Refs #535

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The checkbox message no longer catches clicks meant for content below
it, and the docs say that long messages don't wrap. mapFieldErrors()
gives a general error for problem details without an errors array
instead of throwing, e.g. for untyped callers.

Refs #535

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sourcery-ai

sourcery-ai Bot commented Nov 13, 2026 •

Copy link
Copy Markdown
Contributor

Reviewer's Guide

API

Define and export the RFC 9457 problem-details types and utilities, and expose them from the package root and the component-free ./utils subpath.

  • packages/ecc-utils-design/src/index.ts L5
  • packages/ecc-utils-design/src/utils/index.ts L1
  • packages/ecc-utils-design/src/utils/problem-details.ts L1-331

UI

Add invalid and error states to the form controls, including accessible error-message wiring and invalid styling on select triggers.

  • packages/ecc-utils-design/src/components/checkbox/checkbox.ts L25-30, L35-41 (+2 small edits)
  • packages/ecc-utils-design/src/components/input/input.ts L44-57, L69-80
  • packages/ecc-utils-design/src/components/multi-select/multi-select.ts L51-56, L150-157, L251-258 (+4 small edits)
  • packages/ecc-utils-design/src/components/select/select.ts L40-45, L130-137, L171-185 (+3 small edits)
  • packages/ecc-utils-design/src/components/textarea/textarea.ts L26-31, L36-41, L58-65 (+1 small edits)

Configuration

Build and publish the component-free utilities entry, provide Node-resolution type mappings, and check that the built subpath loads without Lit or components.

  • .changeset/design-field-errors.md L1-9
  • .github/workflows/ci.yml L163-166
  • packages/ecc-utils-design/package.json L19-32
  • scripts/build.js L8 (removed lines), L12, L121
  • scripts/check-design-utils.mjs L1-23
  • scripts/utils.js L2, L54-74

Documentation

Document the response format, utility APIs, form-control error properties, and the build-entry convention for component-free utilities.

  • apps/documentation/pages/docs/contributing.mdx L13-16, L24-33
  • apps/documentation/pages/docs/design/_meta.json L4-5
  • apps/documentation/pages/docs/design/components/checkbox.mdx L130, L196-211
  • apps/documentation/pages/docs/design/components/input.mdx L119-120, L186-201
  • apps/documentation/pages/docs/design/components/multi-select.mdx L194-195, L279-298
  • apps/documentation/pages/docs/design/components/select.mdx L222-223, L265-284
  • apps/documentation/pages/docs/design/components/textarea.mdx L118-119, L203-218
  • apps/documentation/pages/docs/design/field-errors.mdx L1-170

Tests

Cover form-control error states and accessibility, utility parsing and mapping behavior, isolated subpath loading, and build-entry discovery.

  • packages/ecc-utils-design/src/form-controls.test.ts L1-227
  • packages/ecc-utils-design/src/utils/index.test.ts L1-22
  • packages/ecc-utils-design/src/utils/problem-details.test.ts L1-352
  • scripts/utils.test.js L4-9, L38-107

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@read-the-docs-community

read-the-docs-community Bot commented Nov 13, 2026 •

Copy link
Copy Markdown

@codecov

codecov Bot commented Nov 13, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 99.33775% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 74.98%. Comparing base (4655158) to head (29d1097).
⚠️ Report is 1 commits behind head on dev.

Files with missing lines Patch % Lines
...c-utils-design/src/components/checkbox/checkbox.ts 85.71% 2 Missing ⚠️
...ages/ecc-utils-design/src/utils/problem-details.ts 99.69% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##              dev     #576      +/-   ##
==========================================
+ Coverage   73.53%   74.98%   +1.45%     
==========================================
  Files          33       49      +16     
  Lines        8123    11131    +3008     
  Branches      381      582     +201     
==========================================
+ Hits         5973     8347    +2374     
- Misses       2141     2773     +632     
- Partials        9       11       +2     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@uniqueg
uniqueg marked this pull request as ready for review November 13, 2026 09:07
@uniqueg
uniqueg requested a review from anuragxxd as a code owner November 13, 2026 09:07
@sourcery-ai

sourcery-ai Bot commented Nov 13, 2026 •

Copy link
Copy Markdown
Contributor

Linked issues

Checked at commit 29d1097

#535 feat(design): show field errors from the API · 4 of 5 met

# Requirement Status Notes
1 Give the form controls an invalid state and an error message rendered under the control, with accessible invalid and described-by state. 🟡 Partly met Input, textarea, select, and multi-select provide this, but the checkbox has only invalid; its message must be rendered separately.
2 Read and parse RFC 9457 problem details, including standard members, field errors, and extension members. ✅ Met The parser handles the standard members, fragment and plain pointers, parameters, codes, raw entries, and extensions.
3 Map field errors to fields and provide a general error for unmatched errors or responses without field errors or problem details. ✅ Met mapFieldErrors() maps pointers and parameters, reports unmatched errors, and falls back to detail, title, or the supplied default.
4 Document how components use the controls and utilities, with an example. ✅ Met The new Field errors page documents the response format, utilities, and an example form.
5 Test the control states and error mapping, including unknown pointers and responses without errors. ✅ Met The added tests cover the implemented control states and mapping cases, including unmatched locations and responses without field errors.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 9 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="packages/ecc-utils-design/src/utils/problem-details.ts" line_range="157" />
<code_context>
+        method: 'POST',
+        headers: { 'Content-Type': 'application/json' },
+        body: JSON.stringify(body),
+      });
+    } catch {
+      this.generalError = 'The registry could not be reached.';
</code_context>
<issue_to_address>
**API response forges inherited extensions**

When an API response contains an object-valued `__proto__` extension and a caller later reads properties from `problem.extensions`, `extensions[key] = body[key]` invokes the `__proto__` setter when a response contains an object-valued `__proto__` extension, changing the prototype of the returned `extensions` object instead of storing that member as data. Downstream callers then see attacker-controlled inherited properties when reading extension values.

Build `extensions` with a null prototype or define extension keys as own data properties so `__proto__` cannot alter its prototype.
</issue_to_address>

### Comment 2
<location path="apps/documentation/pages/docs/design/field-errors.mdx" line_range="126-127" />
<code_context>
+      FIELDS,
+      `The service could not be registered (${response.status}).`,
+    );
+    this.fieldErrors = mapped.fieldErrors;
+    this.generalError = mapped.generalError ?? '';
+  }
+
</code_context>
<issue_to_address>
**Older responses overwrite newer errors**

When two submissions overlap and the older request resolves last, `submit()` applies the older response’s mapped errors after the newer submission completes, so the form shows outdated validation state or errors after a newer successful response.

Ignore a submission’s result when a newer submission has already started.
</issue_to_address>

### Comment 3
<location path="packages/ecc-utils-design/src/utils/problem-details.ts" line_range="118" />
<code_context>
+      return undefined;
+    }
+  }
+  if (plain !== "" && !plain.startsWith("/")) return undefined;
+  return plain;
+}
</code_context>
<issue_to_address>
**Malformed locations match the wrong field**

When an API returns a pointer with an invalid tilde escape that matches a configured field location, `normalizePointer()` checks only that the pointer starts with `/`, so it accepts invalid RFC 6901 escapes; `mapFieldErrors()` can then attach the error to the wrong field instead of treating it as unmatched.

Reject pointers containing any `~` not followed by `0` or `1`.
</issue_to_address>

### Comment 4
<location path="packages/ecc-utils-design/src/utils/problem-details.ts" line_range="152" />
<code_context>
+ */
+export function parseProblemDetails(body: unknown): ProblemDetails | undefined {
+  if (!isObject(body)) return undefined;
+  if (!["type", "title", "status", "detail", "errors"].some((m) => m in body))
+    return undefined;
+  const extensions: Record<string, unknown> = {};
</code_context>
<issue_to_address>
**Valid problems with only instance are discarded**

When a problem-details response contains `instance` but none of the other members checked by the recognition guard, `parseProblemDetails()` omits `instance` from its recognition check and returns `undefined`, so `readProblemDetails()` discards the valid problem and callers lose its occurrence URI and other parsed details.

Include `instance` among the members that qualify a body as problem details.
</issue_to_address>

### Comment 5
<location path="packages/ecc-utils-design/src/components/input/input.ts" line_range="53" />
<code_context>
     const checkboxClasses = cn(
-      "peer h-4 w-4 shrink-0 rounded-[4px] border border-primary shadow focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
+      "peer h-4 w-4 shrink-0 rounded-[4px] border shadow focus-visible:outline-none focus-visible:ring-1 disabled:cursor-not-allowed disabled:opacity-50",
+      isInvalid
+        ? "border-destructive focus-visible:ring-destructive"
+        : "border-primary focus-visible:ring-ring",
</code_context>
<issue_to_address>
**Input focus rings disappear**

When a user keyboard-focuses an input or textarea, keyboard focus on these controls has no visible ring: the new classes set only the ring color, while `focus-visible:outline-none` removes the browser outline and the old `focus-visible:ring-1` width utility was removed. Users lose the prior focus indicator on both controls.

Keep `focus-visible:ring-1` in the base classes for both controls.

Also at `packages/ecc-utils-design/src/components/textarea/textarea.ts:36`.
</issue_to_address>

### Comment 6
<location path="packages/ecc-utils-design/src/utils/problem-details.ts" line_range="257" />
<code_context>
+      .split("/")
+      .map((key) => key.replace(/~1/g, "/").replace(/~0/g, "~"))
+      .join("/");
+  return location ? `${location}: ${error.detail}` : error.detail;
+}
+
</code_context>
<issue_to_address>
**Empty-key locations are omitted**

When an unmatched error points to an object member with an empty-string key, `describeUnmatched()` treats the valid pointer `/` as an absent location, so the general error shows only the detail and omits which field it concerns.

Distinguish an absent location from a valid location that decodes to the empty string.
</issue_to_address>

### Comment 7
<location path="packages/ecc-utils-design/src/form-controls.test.ts" line_range="142" />
<code_context>
+        expect(button().getAttribute("aria-invalid")).to.equal("true");
+        const describedBy = button().getAttribute("aria-describedby")!;
+        expect(
+          trigger.shadowRoot!.getElementById(describedBy)!.textContent?.trim()
+        ).to.equal("Must be one of: a, b.");
+        expect(button().className).to.contain("border-destructive");
</code_context>
<issue_to_address>
**Duplicate error copy stays exposed**

When a select or multi-select displays an error, `shows the error and marks the trigger invalid` checks only the described-by copy's text at `form-controls.test.ts:142`; removing `hidden` from that copy still passes, leaving an extra exposed message that can be announced alongside the live-region error.

Assert that the described-by copy is hidden as well as containing the error text.
</issue_to_address>

### Comment 8
<location path="packages/ecc-utils-design/src/form-controls.test.ts" line_range="81-83" />
<code_context>
+        el.error = "";
+        await el.updateComplete;
+        const control = el.shadowRoot!.querySelector(selector)!;
+        expect(control.hasAttribute("aria-invalid")).to.equal(false);
+        expect(errorElement(el).textContent?.trim()).to.equal("");
+        expect(control.className).to.not.contain("border-destructive");
+      });
+    });
</code_context>
<issue_to_address>
**Stale error description passes**

When an error is cleared after being set, `clears the error` checks the invalid state, message text and border at `form-controls.test.ts:81-83`, and the select test checks only invalid state and border at `:149-150`; both pass if `aria-describedby="error"` remains after clearing, leaving assistive technology pointed at an empty message.

Assert that `aria-describedby` is absent after clearing the error on each control and select trigger.

Also at `packages/ecc-utils-design/src/form-controls.test.ts:149-150`.
</issue_to_address>

### Comment 9
<location path="packages/ecc-utils-design/src/utils/problem-details.test.ts" line_range="153" />
<code_context>
+    expect(problem?.errors).to.have.length(2);
+    expect(
+      await readProblemDetails(response(body, "application/json"))
+    ).to.not.equal(undefined);
+  });
+
</code_context>
<issue_to_address>
**JSON parsing accepts an empty result**

When the `application/json` branch returns a non-`undefined` result without parsing the body, when `readProblemDetails()` returns a non-`undefined` placeholder for an `application/json` body instead of parsing its problem details, `reads problem+json and JSON bodies` passes at `problem-details.test.ts:153`; it never checks the JSON response's parsed errors or other content.

Assert that the parsed `application/json` response contains the expected errors or another known member from `RFC_EXAMPLE`.
</issue_to_address>

Sourcery assessment

Approval pending. 9 findings to address first.

Blocking findings: packages/ecc-utils-design/src/utils/problem-details.ts:157, apps/documentation/pages/docs/design/field-errors.mdx:127, packages/ecc-utils-design/src/utils/problem-details.ts:118, packages/ecc-utils-design/src/utils/problem-details.ts:152, packages/ecc-utils-design/src/components/input/input.ts:53, and 4 more


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

uniqueg and others added 2 commits November 13, 2026 12:00
- Extension members and field keys named __proto__ are kept as own
  data instead of changing prototypes.
- Pointers with invalid ~ escapes are rejected (RFC 6901).
- Bodies with only instance are recognized as problem details.
- The general error names the member with the empty key (/).
- Docs example: only the latest submission shows its errors.
- Tests check that described-by copies are hidden, that
  aria-describedby is removed with the error, and the parsed JSON.

Refs #535

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adds a changeset for the design package, following the changeset
convention that arrived on dev.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
sourcery-ai[bot]
sourcery-ai Bot previously approved these changes Nov 13, 2026

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sourcery assessment

Approved.

@anuragxxd anuragxxd left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, the problem-details part looks solid.

  • Needed: please add a ./utils subpath export (and a tsup entry). Right now the utilities are only exported from the root, which registers every component, including Ace.
  • Suggested: drop error from the checkbox and keep only invalid. The message doesn't wrap, so it overflows when the checkbox is at the right of a row, as in the TES run-create form.

uniqueg and others added 5 commits November 14, 2026 19:13
Add the subpath export @elixir-cloud/design/utils and build
src/utils/index.ts as a tsup entry, so that apps can import the
problem details utilities without registering every component
(including the Ace-based code editor), as the package root does.

The build entries move to getBuildEntries() in scripts/utils.js,
with tests. The Field errors page imports from the subpath.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Keep only invalid on the checkbox: its message didn't wrap and
overflowed when the checkbox is at the end of a row, as in the TES
run-create form. Docs and changeset say to show the message
elsewhere.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add scripts/check-design-utils.mjs, run in CI after the build: the
built @elixir-cloud/design/utils subpath must export the utilities
and load neither Lit nor components. The build entry tests now clean
up with t.after() and check the design package's real entries.
Document the utils entry convention in the contributing guide and
split the changeset into bullets.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add typesVersions for the utils subpath, so that TypeScript projects
with moduleResolution node, which ignores exports, find its types.
Say in the docs how to show a checkbox's field error: its message
is in fieldErrors, not in generalError.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New gaps against the linked issues

@sourcery-ai
sourcery-ai Bot dismissed their stale review November 14, 2026 19:36

Sourcery withdrew this approval because the latest commits introduced blocking findings.

@uniqueg

uniqueg commented Nov 14, 2026

Copy link
Copy Markdown
Member Author

@anuragxxd thanks, both points are addressed; ready for re-review:

  • ./utils subpath (needed): @elixir-cloud/design/utils is now exported (exports, plus typesVersions for TypeScript's node resolution) and src/utils/index.ts is a tsup entry. The entry list moved to getBuildEntries() in scripts/utils.js (unchanged for every other package), with tests. A new CI step after the build (scripts/check-design-utils.mjs) checks that the subpath loads neither Lit nor any component. The Field errors page now imports from /utils, and the contributing guide describes the convention.
  • Checkbox (suggested): error is removed and only invalid is kept. The checkbox and Field errors pages explain how to show its message instead: set invalid from fieldErrors and render the message next to the label or above the form.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(design): show field errors from the API

2 participants